#include <algorithm>
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <iostream>
#include <optional>
#include <ostream>
#include <stdbool.h>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
#include <set>
#include <format>

#include <regex>

#include <sys/stat.h>
#include <sys/types.h>

#undef LINUX_
#undef DARWIN_
#undef WINDOWS_
#undef UNKNOWN_

#if defined(__linux) || defined(__linux__) || defined(linux)
# define LINUX_
# define PLATFORM_ID "Linux"
#elif defined(__APPLE__)
# define DARWIN_
# define PLATFORM_ID "Darwin"
#elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
# define WINDOWS_
# define PLATFORM_ID "Windows"
#else
# define UNKNOWN_
# define PLATFORM_ID "Unknown"
#endif

#ifdef WINDOWS_
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
inline void sleep(unsigned int sec) { Sleep(sec * 1000); }
#include "Registry.hpp"
#endif

#ifdef LINUX_
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <dirent.h>
#endif

#ifdef DARWIN_
#include <mach-o/dyld.h>
#endif

#define EMPTR ""

#include "spdlog/spdlog.h"
#include "spdlog/common.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/sinks/basic_file_sink.h"

#include "imap.hpp"
#include "zip_longest.hpp"

#include "boost/operators.hpp"
#include "boost/algorithm/string.hpp" // IWYU pragma: keep

#include "yaml-cpp/yaml.h" // IWYU pragma: keep
#include "toml.hpp" // IWYU pragma: keep

#include "cxxopts.hpp"

#include "zip.h"
// TODO: Compressed file index suppport

typedef std::filesystem::path Path;

//using namespace std::literals;

inline std::string wstring2string(std::wstring ws) {
#ifdef WINDOWS_
	// Windows环境下使用WideCharToMultiByte进行转换
	int size = WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), -1, nullptr, 0, nullptr, nullptr);
	std::string result(size, 0);
	WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), -1, result.data(), size, nullptr, nullptr);
	result.pop_back(); // 移除多余的null终止符
	return result;
#else
	// Linux/macOS使用wcstombs转换（需要设置locale）
	size_t size = wcstombs(nullptr, ws.c_str(), 0) + 1;
	std::string result(size, 0);
	wcstombs(result.data(), ws.c_str(), size);
	result.pop_back();
	return result;
#endif
}

inline std::wstring string2wstring(std::string ss) {
#ifdef WINDOWS_
	// Windows环境下使用MultiByteToWideChar进行转换
	int size = MultiByteToWideChar(CP_UTF8, 0, ss.c_str(), -1, nullptr, 0);
	std::wstring result(size, 0);
	MultiByteToWideChar(CP_UTF8, 0, ss.c_str(), -1, result.data(), size);
	result.pop_back();
	return result;
#else
	// Linux/macOS使用mbstowcs转换（需要设置locale）
	size_t size = mbstowcs(nullptr, ss.c_str(), 0) + 1;
	std::wstring result(size, 0);
	mbstowcs(result.data(), ss.c_str(), size);
	result.pop_back();
	return result;
#endif
}

inline bool is_file(const char *path) {
	struct stat buffer;
	if (stat(path, &buffer) == 0)
		return buffer.st_mode & S_IFREG;
	return false;
}

inline bool is_file(const std::string& path) {
	return is_file(path.c_str());
}

inline bool is_file(const std::wstring& path) {
	return is_file(wstring2string(path));
}

inline bool is_file(const Path& path) {
	return is_file(path.string());
}

inline Path program_location() {
#ifdef DARWIN_
	char path[1024];
	uint32_t size = sizeof(path);
	if (_NSGetExecutablePath(path, &size) == 0)
		return p;
#endif
#ifdef LINUX_
	return std::filesystem::read_symlink("/proc/self/exe");
#endif
#ifdef WINDOWS_
	char *path = nullptr;
	if (_get_pgmptr(&path) == 0) {
		return path;
	}
#ifdef _pgmptr
	return _pgmptr;
#endif
	char _execpath[360] = {0};
	GetModuleFileName(NULL, _execpath, 350);
	return _execpath;
#endif
	return std::filesystem::read_symlink("/proc/self/exe");
}

// See https://www.zhihu.com/answer/1072878269
#include <cstdint>
#include <limits>

constexpr auto seed() {
	std::uint64_t shifted = 0;

	for(const auto c : __TIME__) {
		shifted <<= 8;
		shifted |= c;
	}

	return shifted;
}

struct PCG
{
	struct pcg32_random_t {
		std::uint64_t state = 0;
		std::uint64_t inc = seed();
	};

	pcg32_random_t rng;
	typedef std::uint32_t result_type;

	constexpr result_type operator()() {
		return pcg32_random_r();
	}

	static result_type constexpr min() {
		return std::numeric_limits<result_type>::min();
	}

	static result_type constexpr max() {
		return std::numeric_limits<result_type>::min();
	}

private:
	constexpr std::uint32_t pcg32_random_r() {
		std::uint64_t oldstate = rng.state;
		// Advance internal state
		rng.state = oldstate * 6364136223846793005ULL + (rng.inc|1);
		// Calculate output function (XSH RR), uses old state for max ILP
		std::uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
		std::uint32_t rot = oldstate >> 59u;
		return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
	}
};

constexpr auto get_compile_random(int count) {
	PCG pcg;

	while(count-- > 0)
		pcg();

	return pcg();
}

inline std::string val_pipe_run(const char *cmd) {
	std::string result;

#ifdef WINDOWS_
	SECURITY_ATTRIBUTES sa;
	HANDLE hRead, hWrite;
	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
	sa.lpSecurityDescriptor = NULL;
	sa.bInheritHandle = TRUE; // 使子进程继承此句柄

	if (!CreatePipe(&hRead, &hWrite, &sa, 0)) { // 创建匿名管道
		spdlog::error("Creating pipe failed: {}", cmd);
		return EMPTR;
	}

	STARTUPINFO si;
	PROCESS_INFORMATION pi;

	memset(&si, 0, sizeof(si));
	si.cb = sizeof(si);
	si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; // 使用标准输入输出句柄，并隐藏窗口
	si.wShowWindow = SW_HIDE;
	si.hStdOutput = hWrite; // 将标准输出重定向到管道写端
	si.hStdError = hWrite; // 将标准错误重定向到管道写端

	// 启动子进程
	if (!CreateProcess(NULL, (char *)cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
		CloseHandle(hWrite);
		CloseHandle(hRead);
		spdlog::error("Executing command failed: {}", cmd);
		return EMPTR;
	}

	CloseHandle(hWrite);

	for(DWORD dwBytesRead = 0; ; ) {
		char tmp[257] = {0}; // 用于存储命令行输出
		if (!ReadFile(hRead, tmp, 256, &dwBytesRead, NULL) || !dwBytesRead)
			break;
		result += tmp;
	}

	CloseHandle(hRead);

	// 终止子进程
	CloseHandle(pi.hThread);
	TerminateProcess(pi.hProcess, 0);
	CloseHandle(pi.hProcess);

#else
	int pipefd[2];
	pid_t cpid;
	char buf[257] = {0}; // 用于存储命令行输出

	if (pipe(pipefd) == -1) {
		spdlog::error("Creating pipe failed: {}", cmd);
		return EMPTR;
	}
	
	// 创建子进程
	if ((cpid = fork()) == -1) {
		spdlog::error("Fork failed: {}", cmd);
		return EMPTR;
	}

	if (cpid == 0) {
		// 子进程  
		close(pipefd[0]); // 关闭读端

		dup2(pipefd[1], STDOUT_FILENO); // 将标准输出重定向到管道写端
		dup2(pipefd[1], STDERR_FILENO); // 将标准错误重定向到管道写端

		close(pipefd[1]); // 关闭管道的写端（现在标准输出和标准错误已经重定向了，所以不再需要这个描述符）

		// 执行命令  
		if(execlp("sh", "sh", "-c", cmd, NULL)) {
			spdlog::error("Executing command failed: {}", cmd);
			return EMPTR;
		}

	} else {
		// 父进程  
		close(pipefd[1]);  // 关闭写端

		ssize_t n;
		while ((n = read(pipefd[0], buf, 256))) {
			if (n == -1) {
				perror("read");
				exit(EXIT_FAILURE);  
			}

			buf[n] = '\0';
			result += buf;
		}

		close(pipefd[0]);  // 关闭读端，虽然这里已经不需要了，但关闭是一个好习惯

		int status;  
		waitpid(cpid, &status, 0);  // 等待子进程结束
	}
/*
	FILE *fp = popen(cmd, "r");
	if (fp == NULL) {
		spdlog::error("Executing command in pipe failed: {}", cmd);
		return EMPTR;
	}
	
	char tmp[257] = {0}; // 用于存储命令行输出
	while(fgets(tmp, 256, fp)){
		result += tmp;
		memset(tmp, 0, 256);
	}

	pclose(fp);
*/
#endif //WINDOWS_

	return result;
}

// See https://www.cppstories.com/2018/07/string-view-perf-followup/
std::vector<std::string_view> split_sv(const std::string_view& s, const std::string_view& delimiters) {
	std::vector<std::string_view> tokens;
	std::string_view::size_type lastPos = s.find_first_not_of(delimiters, 0);
	std::string_view::size_type pos = s.find_first_of(delimiters, lastPos);
	while (std::string::npos != pos || std::string::npos != lastPos) {
		tokens.emplace_back(s.substr(lastPos, pos - lastPos));
		lastPos = s.find_first_not_of(delimiters, pos);
		pos = s.find_first_of(delimiters, lastPos);
	}
	return tokens;
}

// From https://codereview.stackexchange.com/questions/142902/simple-string-joiner-in-modern-c
template<typename InputIt>
std::string joins(
	InputIt begin, InputIt end,
	const std::string_view& separator = ", ",
	const std::string_view& concluder = "")
{
	std::ostringstream ss;

	if(begin != end)
		ss << *begin++;

	while(begin != end)
		ss << separator << *begin++;

	ss << concluder;

	return ss.str();
}

template<typename T>
std::string joins(
	T& iterable,
	const std::string_view& separator = ", ",
	const std::string_view& concluder = "")
{
	return joins(iterable.begin(), iterable.end(), separator, concluder);
}

class EnvMgr
{
private:
	std::unordered_map<std::string, char *> _raw_cache;

public:
	std::optional<char *> getenv(const std::string name) {
		auto _cache_v = this->_raw_cache.find(name);
		if (_cache_v != _raw_cache.end()) {
			// Value in cache
			return _cache_v->second;
		}

		auto value_raw = std::getenv(name.c_str());
		if (value_raw == NULL) 
			return std::nullopt;
		this->_raw_cache[name] = value_raw;
		return value_raw;
	}

	auto getPath() {
		std::string _gph = this->getenv("PATH").value();
		// match each path
		std::regex pattern(R"((?:[a-zA-Z]:[^;]*)|(?:\\\\[^;]*)|(?:/[^:]*))", std::regex_constants::ECMAScript | std::regex_constants::optimize);

		std::smatch m;

		std::vector<std::string> result;

		for (auto citer = _gph.cbegin(); regex_search(citer, _gph.cend(), m, pattern); citer = m[0].second)
			result.push_back(m.str(0));

		return result;
	}

	auto addPath(const std::string &dir) {
		int code;
		std::string _raw_Path = this->getenv("PATH").value();
#ifdef WINDOWS_
		_raw_Path += ";";
#else
		_raw_Path += ":";
#endif
		_raw_Path += dir;
		char *new_Path = (char *)calloc(_raw_Path.size()+6, 1);
		memcpy(new_Path, _raw_Path.c_str(), _raw_Path.size());
#ifdef WINDOWS_
		code = SetEnvironmentVariable("PATH", new_Path);
		if (code) {
			sprintf(new_Path, "PATH=%s", new_Path);
			code = putenv(new_Path);
		}
#else
		sprintf(new_Path, "PATH=%s", new_Path);
		code = setenv("Path", new_Path, 1);
#endif
		free(new_Path);
		return code;
	}
};

class version: boost::operators<version>
{
private:
	std::vector<short> _ver;
	
public:
	version(const short i=0) {
		this->_ver.emplace_back(i);
	}

	version(const std::string_view s) {
		for(auto i : split_sv(s, "."))
			this->_ver.emplace_back(std::stoi(std::string{i}));
	}

	version(const version& v) {
		for(auto i : v._ver)
			this->_ver.emplace_back(i);
	}

	bool operator==(const version& other) const {
		if (this->_ver.size() == other._ver.size())
			return this->_ver == other._ver;
			
		for (auto&& [i, j] : iter::zip_longest(this->_ver, other._ver))
			if ((i ? *i : 0) != (j ? *j : 0))
				return false;

		return true;
	}

	bool operator<(const version& other) const {
		for (auto&& [i, j] : iter::zip_longest(this->_ver, other._ver)) {
			auto a = (i ? *i : 0), b = (j ? *j : 0);
			if (a != b) return a < b;
		}

		return false;
	}

	version operator-() const {
		version new_ver;
		for(auto i : this->_ver)
			new_ver._ver.emplace_back(-i);
		return new_ver;
	}

	bool empty() const {
		return this->_ver.empty();
	}

	size_t _hash() const {
		size_t h = 0;
		auto a = 63689, b = 378551;
		for(auto i : this->_ver)
			while (i) {
				h = h*a + (i & 0xff);
				a *= b;
				i >>= 8;
			}
		return h;
	}

	std::string_view sv() const {
		return std::string_view{joins(this->_ver, ".")};
	}

	std::string string() const {
		return std::string(this->sv());
	}
};

namespace std {
    template<>
    struct hash<version> {
        size_t operator()(const version& _v) const noexcept {
            return _v._hash();
        }
    };
}

std::unordered_map<std::string, std::string> load_jmf_fom_jar(const char *jar_file) {
	void *buf = NULL;
	size_t bufsize = 0;

	struct zip_t *zip = zip_open(jar_file, 0, 'r');
	{
		zip_entry_open(zip, "META-INF/MANIFEST.MF");
		{
			zip_entry_read(zip, &buf, &bufsize);
		}
		zip_entry_close(zip);
	}
	zip_close(zip);

	std::unordered_map<std::string, std::string> result;

	for(auto i : YAML::Load(std::string{(char *)buf, bufsize}))
		result[i.first.as<std::string>()] = i.second.as<std::string>();

	free(buf);

	return result;
}

class HMCLsMgr
{
private:
	std::set<std::string> _hmcls;

public:
	HMCLsMgr(const std::string& hp) {
		if (!std::filesystem::exists(hp))
			spdlog::warn("Path for HMCLs does not not exists: `{}`", hp);

		else if (std::filesystem::is_directory(hp))
			for(auto& p : std::filesystem::directory_iterator(hp)) {
				if (is_file(p) && boost::iends_with(p.path().extension().string(), ".jar")) {
					this->_hmcls.insert(std::format("{}", p.path().string()));
				}
			}

		else if (!boost::iends_with(hp, ".jar"))
			spdlog::warn("`{}` is not jar file!", hp);

		else
			this->_hmcls.insert(hp);
	}

	bool empty() const {
		return this->_hmcls.empty();
	}

	std::string match(const std::string& spec) const {
		if (is_file(spec)) {
			return spec;
		} else if (std::filesystem::is_directory(spec))
			return HMCLsMgr(spec).match("lastest");

		std::string_view status, verr;
		if (spec.find("-") != spec.npos) {
			auto x = split_sv(spec, "-");
			status = x[0];
			verr = x[1];
		} else {
			status = "";
			verr = spec;
		}
		
		std::unordered_map<version, std::string> ver_jars;
		for(auto&& i : this->_hmcls) {
			auto jpm = load_jmf_fom_jar(i.data());
			if (status.empty() || boost::iequals(status, jpm["Build-Channel"]))
				ver_jars[version(jpm["Implementation-Version"])] = i;
		}

		version tv;
		if (verr == "lastest")
			for(auto&& [k, v] : ver_jars)
				tv = std::max(tv, k);
		
		else if (verr == "oldest") {
			tv = 16383;
			for(auto&& [k, v] : ver_jars)
				tv = std::min(tv, k);
		}

		else if (ver_jars.find(version(verr)) != ver_jars.end())
			return ver_jars[version(verr)];
		
		else 
			spdlog::error("Unsupported opinion `{}`, can be `lastest` / `oldest` or a specific version number.", verr);
		
		if (tv.empty())
			return {};
		return ver_jars[tv];
	}

	std::string match(const std::vector<std::string> sps) const {
		for(auto&& i : iter::imap([&](auto sp){return this->match(sp);}, sps))
			if (!i.empty()) return i;
		return {};
	}

	std::optional<std::string> get_internal_ver(Path hmcljar_path) {
		if (!std::filesystem::exists(hmcljar_path))
			return {};

		auto jpm = load_jmf_fom_jar(hmcljar_path.string().c_str());

		return jpm["Implementation-Version"];
	}
};

std::string JFxConf(Path JFxdir) {
	std::string result = std::format("--module-path \"{}\"", JFxdir.string());

	std::regex pattern(R"(module-info\.java[^M]*Module[^j]*(javafx\.[a-z]+))", std::regex_constants::ECMAScript | std::regex_constants::optimize);

	for(auto& p : std::filesystem::directory_iterator(JFxdir))
		if (is_file(p) && boost::iends_with(p.path().extension().string(), ".jar")) {
			void *buf = NULL;
			size_t bufsize = 0;

			struct zip_t *zip = zip_open(p.path().string().c_str(), 0, 'r');
			{
				zip_entry_open(zip, "module-info.class");
				{
					zip_entry_read(zip, &buf, &bufsize);
				}
				zip_entry_close(zip);
			}
			zip_close(zip);

			std::smatch m;
			auto bufstr = std::string{(char *)buf, bufsize};
			if (std::regex_search(bufstr, m, pattern)) {
				result = std::format("{} --add-modules {}", result, m.str(1));

			} else {
				spdlog::warn("Failed matching JFX: {}", p.path().string());
			}

			free(buf);
		}

	return result;
}

bool file_in_path(const char *cmd, std::string& extpath) {
#ifdef WINDOWS_
	for (std::string suf : {".exe", ".com", ".bat", ".cmd"}){
		if (is_file(cmd + suf)) {
			extpath = cmd + suf;
			return true;
		}
	}
#else
	if (is_file(cmd)) {
		extpath = cmd;
		return true;
	}
#endif
	
	for (auto &i : EnvMgr().getPath()) {
		auto p = Path(i)/cmd;
		if (is_file(p)) {
			extpath = p.string();
			return true;
		}
#ifdef WINDOWS_
		for (auto suf : {".exe", ".com", ".bat", ".cmd"}){
			if (is_file(p.string() + suf)) {
				extpath = p.string() + suf;
				return true;
			}
		}
#endif
	}

	return false;
}

class PossJav
{
private:
	version _ver;

public:
	std::string Jav_path;

	PossJav() {};

	PossJav(std::string JavPath) {
		this->set_javpath(JavPath);
	}

	PossJav(std::string JavPath, std::string JVerStr) {
		this->set_javpath_with_ver_str(JavPath, JVerStr);
	}

	PossJav(std::string JavPath, version JVer) {
		this->set_javpath_with_ver(JavPath, JVer);
	}

	void set_javpath(std::string JavPath) {
		this->set_javpath_with_ver_str(JavPath, get_version_str());
	}

	void set_javpath_with_ver_str(std::string JavPath, std::string JVerStr) {
		// JVerStr is the return of java -version
		this->set_javpath_with_ver(JavPath, solve_version_from_str(JVerStr));
	}

	void set_javpath_with_ver(std::string JavPath, version JVer) {
		this->Jav_path = JavPath;
		this->_ver = JVer;
	}

	auto check_version(bool *pattern(version a), const std::string_view& ext_args) {
		// Pass a function
		//TODO
		return 0;
	}

	std::string get_version_str(const std::string_view& ext_args) {
		spdlog::info("Testing {}", this->Jav_path);
		return val_pipe_run(std::format("{} {} -version", this->Jav_path.data(), ext_args.data()).c_str());
	}

	std::string get_version_str() {
		return get_version_str("");
	}

	version solve_version_from_str(const std::string& version_str) {
		std::smatch m;
		if (std::regex_search(version_str, m, std::regex("(?:openjdk|java) version \"([^ ]+)\""))) {
			raise(1);
		}
		
		std::string _ver = m.str(1);
		version ver = std::string_view{_ver};
		spdlog::trace("Version of java is: {}", ver.string());

		return ver;
	}

	auto get_version(const std::string_view& args) {
		return solve_version_from_str(get_version_str(args));
	}

	auto add_to_path() {
		return EnvMgr().addPath(this->Jav_path);
	}
};

class JFinder
{
private:
	PossJav _jav;
	std::string _launch_args;
	std::vector<PossJav> _possible_javs;

	const char *_execfile = "java";
	std::vector<const char *> locations = {"/usr/bin", "/usr/lib", "/usr/java"};

public:
	bool short_circuit_search = false;
	bool auto_add_env = true;

	JFinder() {
#if defined(WINDOWS_)
		this->_execfile = "java.exe";
		this->locations = {R"(C:\Program Files\Java)", R"(C:\Program Files (x86)\Java)"};

#elif defined(DARWIN_)
		this->locations = {"/Library/Java/JavaVirtualMachines", "/usr/libexec/java_home"};

#elif defined(LINUX_)
		this->locations = {"/usr/bin", "/usr/lib/", "/usr/java", "/opt/sun"};

#endif
	}

	int get_default() {
		auto jh = EnvMgr().getenv("JAVA_HOME");

		// 1. find from env var java home
		if (jh.has_value())
			if (!this->search_java_home(jh.value()))
				return 0;

		// 2. find from env path
		std::string sf;
		if (file_in_path(this->_execfile, sf)) {
			this->_jav = sf;
			return 0;
		}

#ifdef WINDOWS_
		// 3.1. find from registry on windows
		for (auto &location : std::vector<std::wstring>{
			LR"(SOFTWARE\JavaSoft\Java Runtime Environment)",
			LR"(SOFTWARE\JavaSoft\JRE)",
			LR"(SOFTWARE\JavaSoft\Java Development Kit)",
			LR"(SOFTWARE\JavaSoft\JDK)"}) {
			try {
				auto jreKey = m4x1m1l14n::Registry::LocalMachine->Open(location);
				auto cv = jreKey->GetString(L"CurrentVersion");
				auto versionKey = jreKey->Open(cv);

				cv = versionKey->GetString(L"JavaHome");

				if (!this->search_java_home(wstring2string(cv)))
					return 0;
			}
			catch (const std::exception &ec) {
				spdlog::error("Find JAVA from registry failed: {}", wstring2string(location));
			}
		}
#endif

		// 3.2. find from default locations
		for(auto i : this->locations)
			if (!this->search_location(i)) {
				return 0;
			}

		// 4. still cant find java -> return error code 1
		return 1;
	}

	auto find_possible_homes(const std::vector<Path>& parents) {
		std::vector<std::string_view> homes;
		for(auto p : parents) {
			//TODO:NotImplemented
		}
		return homes;
	}

	int search_java_home(std::string_view jh) {
		return search_location(jh);
	}

	int search_location(Path jh) {
		if (std::filesystem::is_directory(jh)) {
			for(auto&& i : std::filesystem::directory_iterator(jh)) {
				if (!search_location(i))
					return 0;
			}
	
		} else if (is_file(jh) && boost::iequals(jh.filename().c_str(), this->_execfile)) {
			if (check_java(jh.string())) {
				this->_jav = jh.string();
				return 0;

			} else {
				spdlog::warn("JAVA not found: {}", jh.string());
			}
		}
		
		return 1;
	}

	bool check_java(const std::string_view& javpath) {
		if (check_java_version(javpath))
			return true;
		return false;
	}

	bool check_java_version(const std::string_view& javpath) {
		return check_java_version(javpath, this->_launch_args);
	}

	bool check_java_version(const std::string_view& javpath, const std::string_view& args) {
		std::string vres = val_pipe_run(std::format("{} {} -version", javpath.data(), args.data()).c_str());

		std::smatch m;
		if (std::regex_search(vres, m, std::regex("(?:openjdk|java) version \"([^ ]+)\""))) {
			return true;
		}

		return false;
	}

	bool is_found() const {
		return std::filesystem::exists(this->_jav.Jav_path);
	}

	std::string_view sv() const {
		return this->_jav.Jav_path;
	}

	std::string string() const {
		return this->_jav.Jav_path;
	}
};

#include "get_conf.h"

const auto load_cfg(std::string path) {
	if (is_file(path))
		return toml::parse(path);

	spdlog::warn("TOML config file does not exists. Automatically generating: {}", std::filesystem::absolute(path).string());
	
	FILE *fp = fopen(path.c_str(), "wb");
	fputs(default_config, fp);
	fclose(fp);

	return load_cfg(path);
}

const auto load_cfg(Path path) {
	return load_cfg(path.string());
}

// Complie constant
inline const auto _hmcl_sp_s = std::format("HMCL1R{}", std::to_string(get_compile_random(8)));
#define _hmcl_pid_f_s (std::format("/tmp/{}.pid", _hmcl_sp_s))
inline auto _x_linux_set_pid_del_at_exit = false;
inline auto _debug = false;

void funcatlast() {
#ifdef WINDOWS_
	if (_debug)
		sleep(100000);
#else
	//Used to clean pid file
	if (_x_linux_set_pid_del_at_exit)
		std::filesystem::remove(_hmcl_pid_f_s);
	if (_debug)
		pause();

#endif
}

int lock_one_exec() {
	return 0;
}

int main(int argc, char *argv[])
{
	std::atexit(funcatlast);

	cxxopts::Options options(argv[0], "HMCL Launcher description");

	auto _pgd = program_location().parent_path();

	options.add_options()
		("c,config", "HMCL Launcher Configurtion file path", cxxopts::value<std::string>()->default_value((_pgd/"config.toml").string()))
		("d,debug", "Enable force debug mode", cxxopts::value<bool>()->default_value("false"))
		("p,pass", "Argument(s) about to pass to hmcl.jar directly while launching", cxxopts::value<std::vector<std::string>>()->default_value({}))
		("h,help", "Print usage");

	// TODO: pass arg to hmcl jar
	options.parse_positional({"p,pass"});

	auto opts_res = options.parse(argc, argv);

	if (opts_res.count("help")) {
		std::cout << options.help() << std::endl;
		return 0;
	}

	auto config = load_cfg(opts_res["config"].as<std::string>());
	// TODO: Config trace handle; Config check

	auto log_path=toml::find<std::string>(config, "logfile");
	if (toml::find<bool>(config, "enable-file-logging")) {
		try {
			spdlog::default_logger()->sinks().push_back(
				std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_path));
		} catch (const spdlog::spdlog_ex &ex) {
			spdlog::error("Log file init failed: {}", ex.what());
		}
	}

	if (toml::find<bool>(config, "debug-mode"))
		_debug = true;
	if (_debug || opts_res.count("debug")) {
		spdlog::set_level(spdlog::level::trace);
#ifdef WINDOWS_
		if (!AttachConsole(ATTACH_PARENT_PROCESS)) {
			if (GetLastError() == ERROR_ACCESS_DENIED) {
				AllocConsole();
			}
		}
#endif
		auto console = spdlog::stdout_color_mt("console");
		auto err_logger = spdlog::stderr_color_mt("stderr");
		spdlog::set_default_logger(console);
		
		if (_debug) {
			spdlog::info("Config file set debug-mode = true");
			spdlog::info("Force DEBUG mode");
		} else {
			_debug = true;
		}
		
		spdlog::get("console")->info("Under DEBUG mode");
	}

	spdlog::info("--- Starting HMCL Launcher ---");

	if (toml::find<bool>(config, "force-program-wd")) {
		std::filesystem::current_path(_pgd);
		spdlog::info("Force using working directory: {}", std::filesystem::current_path().string());

	} else {
		spdlog::info("Current working directory: {}", std::filesystem::current_path().string());
	}

	if (toml::find<bool>(config, "one-launcher-lock")) {
#ifdef WINDOWS_
		HANDLE g_hEvent = CreateEvent(NULL, 0, 0, _hmcl_sp_s.c_str());
		SetEvent(g_hEvent);
		if (g_hEvent && ERROR_ALREADY_EXISTS == GetLastError()) {
			spdlog::error("You enabled `one-launcher-lock`! Only one launcher allowed, this will be closed.");
			return 1;
		}
#else
		const auto pid_file = _hmcl_pid_f_s;
		spdlog::info("launcher.lock creating: {}", pid_file);

		const auto pid_exists = [&](int pid) {
			if (pid < 0)
				return false;
			if (pid == 0)
				// According to "man 2 kill" PID 0 refers to every process
				// in the process group of the calling process.
				// On certain systems 0 is a valid PID but we have no way
				// to know that in a portable fashion.
				spdlog::error("invalid PID 0");
			
			if (!kill(pid, 0))
				return true;

			if (errno == ESRCH)
				// ESRCH == No such process
				return false;
			if (errno == EPERM)
				// EPERM clearly means there's a process to deny access to
				return true;
			else
				// According to "man 2 kill" possible error values are
				// (EINVAL, EPERM, ESRCH)
				spdlog::error("");
			
			return false;
		};

		const auto writePidFile = [&](const char *szPidFile) {
			/* 获取文件描述符 */
			int pidfile = open(szPidFile, O_WRONLY|O_CREAT|O_TRUNC, 0600);

			if (pidfile < 0) {
				printf("pidfile is %d", pidfile);
				return -1;
			}
		
			/* 锁定文件，如果失败则说明文件已被锁，存在一个正在运行的进程，程序直接退出 */
			if (lockf(pidfile, F_TLOCK, 0) < 0) {
				fprintf(stderr, "File locked ! Can not Open Pid File: %s", szPidFile);
				return -1;
			}

			/* 锁定文件成功后，会一直持有这把锁，知道进程退出，或者手动 close 文件
			然后将进程的进程号写入到 pid 文件*/
			std::string st = std::to_string(getpid());
			ssize_t retlen = write(pidfile, st.c_str(), st.size());

			return 0;
		};

		if (is_file(pid_file)) {
			_x_linux_set_pid_del_at_exit = true;

			pid_t cpid;
			std::ifstream  fin;
			fin.open(pid_file);
			if(!fin.is_open())
				spdlog::warn(".lock file opened failed: {}", pid_file);
			fin >> cpid;
			spdlog::trace("Read PID from file: {}", cpid);

			if (pid_exists(cpid)) {
				spdlog::info("PID existed: {}", cpid);
				spdlog::error("You enabled `one-launcher-lock`! Only one launcher allowed, this will be closed.");
				return 1;
			}
		}

		if (writePidFile(pid_file.c_str()) == -1) {
			spdlog::error("Failed to write PID file.");
			spdlog::error("You enabled `one-launcher-lock`! Only one launcher allowed, this will be closed.");
			return 1;
		} else {
			spdlog::info("PID file writed.");
		}
		
#endif
		spdlog::warn("One launcher had been locked");
	}

	std::vector<std::string_view> launch_args;

	JFinder Jav;
	Jav.short_circuit_search = toml::find<bool>(config, "short-circuit-search");
	for (auto&& p : toml::find<std::vector<std::string_view>>(config, "jrepath")) {
		if (p == "auto") {
			if (Jav.get_default()) {
				spdlog::warn("Cant find JAVA from default");
				if (Jav.search_java_home("java/")) {
					spdlog::warn("Cant find JAVA from original ./java/*");
					spdlog::error("Failed to find JAVA automatically");
				}
			}

		} else {
			if (!std::filesystem::exists(p)) {
				spdlog::warn("Provided path not exist: {}", p);
				continue;
			}
			if (Jav.search_location(p)) {
				spdlog::error("Cant find JAVA from: {}", std::string(p));
			}
		}

		if(Jav.is_found()) {
			spdlog::info("JAVA selected: {}", Jav.string());
			break;
		}
	}
	
	if(!Jav.is_found()) {
		auto _jrepath = toml::find<std::vector<std::string_view>>(config, "jrepath");
		spdlog::error("Cant find JAVA installed: [{}]. "
			"Please ensure your JAVA_HOME "
			"environment variable is pointing "
			"to correct installation", joins(_jrepath));
		return 1;
	}

	launch_args.emplace_back(Jav.sv());

	HMCLsMgr hmcl("hmcl-rt");
	auto hmcljp = config["hmcl-spec"].is_array()
		? hmcl.match(toml::find<std::vector<std::string>>(config, "hmcl-spec"))
		: hmcl.match(toml::find<std::string>(config, "hmcl-spec"));

	if (hmcljp.empty()) {
		spdlog::critical("No suitable HMCL Jar Found!'");
		return 1;
	}

	std::string home_conf;
	if (!config["hmcl-home"].is_boolean()) {
		auto hmcl_home = toml::find<std::string>(config, "hmcl-home");
		home_conf = std::format("-Dhmcl.home=\"{}\"", hmcl_home);
		launch_args.emplace_back(home_conf);
		spdlog::info("Using custom HMCL home: `{}`", hmcl_home);
	}

	std::string jfx_conf;
	if (config["optional-javafx"].is_string()) {
		auto ol_jfx = toml::find<std::string>(config, "optional-javafx");
		if (ol_jfx == "auto") {
			//TODO:NotImplemented

		} else if (std::filesystem::exists(ol_jfx)) {
			jfx_conf = JFxConf(std::filesystem::absolute(ol_jfx));
			launch_args.emplace_back(jfx_conf);

		} else {
			spdlog::warn("Path for JavaFX not found: {}", ol_jfx);

		}
	}

	for(auto&& i : toml::find<std::vector<std::string_view>>(config, "extra-java-args")) {
		launch_args.emplace_back(i);
		spdlog::info("Using extra command line arguments: `{}`", i);
	}
	
	auto i_Ver = hmcl.get_internal_ver(hmcljp).value_or("Unknown");
	hmcljp = std::format(R"("{}")", hmcljp);
	spdlog::info("HMCL (Internal Ver: `{}`) Jar Choised: {}", i_Ver, hmcljp);
	launch_args.emplace_back("-jar");
	launch_args.emplace_back(hmcljp);

	// Generate command
	auto cmd = joins(launch_args, " ");
	spdlog::info("Executing launching command: `{}`", cmd);

#ifdef WINDOWS_
	if (toml::find<bool>(config, "sub-process"))
		val_pipe_run(cmd.c_str());
	else
		WinExec(cmd.c_str(), SW_HIDE);

#else
	if (toml::find<bool>(config, "sub-process"))
		val_pipe_run(cmd.c_str());
	else
		system(std::format("{} &", cmd).c_str());
/*
	if (!toml::find<bool>(config, "sub-process"))
		cmd += " &";
	system(cmd.c_str());
*/
#endif

	spdlog::info("Launching done");

	return 0;
}
